/**
 *  @file BufferedReadStream.cpp
 */
#include "BufferedReadStream.h"
#include "../common/CppMemPool.h"
#include "../collections/CircularBuffer.h"
#include "../lang/IntCheck.h"
#include "../text/CFString.h"
#include "../common/UtilFuncT.h"

using namespace cppflib::collections;

namespace cppflib
{

namespace io
{

BufferedReadStream::BufferedReadStream(void)
{
   this->pOrgStream = NULL;
   this->pCirBuf = NULL;
   this->readPos = 0;
}

BufferedReadStream::BufferedReadStream(Stream *pStream, sz_t bufferSize)
{
   this->pOrgStream = pStream;
   cppmempool::Retain(this->pOrgStream);
   this->pCirBuf = new CircularBuffer(bufferSize);
   cppmempool::Retain(this->pCirBuf);
   this->readPos = 0;
}

BufferedReadStream::BufferedReadStream(Stream *pStream)
{
   const u32_t DEFAULT_READ_BUFFER_SIZE = 256;

   this->pOrgStream = pStream;
   cppmempool::Retain(this->pOrgStream);
   this->pCirBuf = new CircularBuffer(DEFAULT_READ_BUFFER_SIZE);
   cppmempool::Retain(this->pCirBuf);
   this->readPos = 0;
}

BufferedReadStream::~BufferedReadStream(void)
{
   Close();
}

void BufferedReadStream::Close()
{
   if (this->pOrgStream) {
      cppmempool::Release(this->pOrgStream);
      this->pOrgStream = NULL;
   }

   if (this->pCirBuf) {
      cppmempool::Release(this->pCirBuf);
      this->pCirBuf = NULL;
   }
}

sz_t BufferedReadStream::CanReadNoBlocking()
{
   return (pCirBuf->GetValidLength() - readPos) + pOrgStream->CanReadNoBlocking();
}

/**
 *  Read data from the underlying stream and make a copy to the internal buffer, too
 */
int BufferedReadStream::Read(collections::ArrayByte &buffer)
{
   return Read(buffer, 0, static_cast<int>(buffer.GetSize()));
}

int BufferedReadStream::Read(collections::ArrayByte &buffer, int offset, int count)
{
   if (count < 0 || offset < 0 || offset >= static_cast<int>(buffer.GetSize()))
      return 0;

   // use the smaller one
   count = cppflib::Min<int>(count, static_cast<int>(buffer.GetSize() - offset));

   int totalRead = 0;
   if (readPos < pCirBuf->GetValidLength()) {
      // copy unread portion in internal buffer

      // bytes left in binbuf
      sz_t bytesRead = pCirBuf->GetValidLength() - readPos;
      // read the smaller number
      bytesRead = cppflib::Min<sz_t>(static_cast<sz_t>(count), bytesRead);
      u8_t * destPtr = buffer.GetRawPtr() + offset;
      pCirBuf->GetData(destPtr, bytesRead, readPos);
      
      // update
      count -= static_cast<int>(bytesRead);
      offset += static_cast<int>(bytesRead);
      totalRead += static_cast<int>(bytesRead);
      readPos += static_cast<int>(bytesRead);
   }

   // nothing is read or buffer not full and underlying stream can be read without blocking
   if (totalRead == 0 || (count > 0  && pOrgStream->CanReadNoBlocking() > 0)) {  
      int nrRead = pOrgStream->Read(buffer, offset, count);
      if (nrRead > 0) {
         // copy data back to the circular buffer
         const u8_t * srcPtr = buffer.GetRawPtr() + offset;
         pCirBuf->AppendData(srcPtr, nrRead);
         readPos = pCirBuf->GetValidLength();

         totalRead += nrRead;
      }
   }
 
   return totalRead;
}

int BufferedReadStream::Read()
{
   int ret = -1;
   if (readPos < pCirBuf->GetValidLength()) {
      u8_t tempBuf;
      pCirBuf->GetData(&tempBuf, 1, readPos++);
      ret = tempBuf & 0xff;
   }
   else {
      ret = pOrgStream->Read();
      if (ret >= 0) {
         u8_t data = static_cast<u8_t>(ret);
         pCirBuf->AppendData(&data, 1);
         readPos = pCirBuf->GetValidLength();
      }
   }

   return ret;
}

i64_t BufferedReadStream::Seek(i64_t offset, seekorigin_t origin)
{
   if (origin != Stream::S_CUR)
      return -1;

   if (lang::CheckOverflow(lang::AOP_ADD, static_cast<i64_t>(readPos), offset))
      return -1;

   i64_t pos = static_cast<i64_t>(readPos) + offset;   
   if (pos < 0 || pos >= static_cast<i64_t>(pCirBuf->GetValidLength())) // can't move beyond buffer
      return -1;

   readPos = static_cast<u32_t>(pos);
   return readPos;  // the return value is incorrect actually
}


} // end of namespace io

} // end of namespace cppflib
